using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Collections;
using Tomahawk.Runtime.Rendering.Objects;
using Tomahawk.Runtime.Rendering;
using Tomahawk.Runtime.Rendering.Materials;

namespace Tomahawk.Runtime.Scene
{
    /// <summary>
    /// This class represent an hierarchical structure (tree like) of nodes that represent 
    /// a complex graphic scene.
    /// </summary>
    public class SceneManager
    {
 
        /// <summary>
        /// Node dictionary
        /// </summary>
        Dictionary<String, SceneNode> nodeList;

        /// <summary>
        /// Lights dictionary
        /// </summary>
        List<Light> shadowLightList;
        
        /// <summary>
        /// Lights dictionary
        /// </summary>
        List<Light> normalLightList;

        /// <summary>
        /// Root node
        /// </summary>
        SceneNode rootNode = null;

        /// <summary>
        /// Access to the root node
        /// </summary>
        public SceneNode RootNode
        {
            get { return rootNode; }
        }

        /// <summary>
        /// Visit modes for the scene
        /// </summary>
        [Flags]
        internal enum VisitSceneFlags
        {
            None                = 0x00, // No node will be visited
            AllNodes            = 0x01, // All nodes will be visited
            AllVisibleNodes     = 0x02, // Only visible nodes will be visited
            AllRenderableNodes  = 0x04, // Only renderable nodes will be visited
        }

        /// <summary>
        /// Callback for visiting the scene
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        internal delegate bool VisitSceneDelegate(SceneNode node);

        /// <summary>
        /// Public Constructor
        /// </summary>
        public SceneManager()
        {
            //create node list
            this.nodeList = new Dictionary<string, SceneNode>();

            //create light list
            this.shadowLightList = new List<Light>();
            this.normalLightList = new List<Light>();

            //create root scene node
            this.rootNode = SceneNode.CreateRootSceneNode();
        }

        Matrix _RootWorldMatrix = Matrix.Identity;

        /// <summary>
        /// Update the scene tree world matrix information
        /// </summary>
        public void Update()
        {
            //reset the root world matrix
            _RootWorldMatrix = Matrix.Identity;

            //update the entire scene
            if (this.rootNode != null)
                this.rootNode.Update(ref _RootWorldMatrix, false);
        }

        /// <summary>
        /// Prepares the scene for rendering
        /// </summary>
        /// <param name="device"></param>
        internal void PreRender(GraphicsDevice device)
        {
            //get the camera and the effect environment
            SASRenderEnvironment env = Engine.Instance.RenderManager.Environment;
            Camera cam = Engine.Instance.CameraManager.ActiveCamera;

            //TODO: obtener directamente por referencia!!
            Matrix view = cam.ViewMatrix;
            Matrix projection = cam.ProjectionMatrix;
            Vector3 eyeposition = cam.EyePosition;

            //set scene global parameters
            env.projection = projection;
            env.view = view;
        }

        /// <summary>
        /// Render all nodes
        /// </summary>
        internal void Render(GraphicsDevice device)
        {
            //render the scene!
            if (this.rootNode != null)
                this.rootNode.Render(device, true);
        }

        internal void Visit(VisitSceneDelegate callback, VisitSceneFlags flags)
        {
            //visit the scene!
            if (this.rootNode != null)
                this.rootNode.Visit(callback, flags);
        }

        /// <summary>
        /// Final steps after rendering the scene
        /// </summary>
        /// <param name="device"></param>
        internal void PostRender(GraphicsDevice device)
        {
        }

        /// <summary>
        /// Add a node to the node list
        /// </summary>
        internal void RegisterNode(SceneNode node, string nodeName)
        {
            this.nodeList.Add(nodeName, node);
        }

        /// <summary>
        /// Remove a node
        /// </summary>
        internal void UnregisterNode(SceneNode node)
        {
            string key = "";
            foreach (string nodekey in this.nodeList.Keys)
            {
                if (this.nodeList[nodekey] == node)
                {
                    key = nodekey;
                    break;
                }
            }
            if (key != "") this.nodeList.Remove(key);
        }

        /// <summary>
        /// Return a node from the node list
        /// </summary>
        public SceneNode GetNodeByName(string nodeName)
        {
            return (SceneNode)this.nodeList[nodeName];
        }

        #region "Closest Light Detection"

        /// <summary>
        /// Activate the ClosestLight, using the worldMatrix of the object
        /// </summary>
        internal Light GetClosestLight(Matrix worldMatrix, bool isShadowCasting)
        {
            //TO-DO: Need a better algorithm for closest light detection
            Light retVal = null;

            float shortestDistance = -1;

            List<Light> lightList = this.normalLightList;
            if (isShadowCasting)
                lightList = this.shadowLightList;

            foreach (Light light in lightList)
            {
                float distance = ((Vector3)(light.EyePosition + worldMatrix.Translation)).Length();

                if (shortestDistance == -1)
                {
                    shortestDistance = distance;
                    retVal = light;
                }
                else if (distance < shortestDistance)
                {
                    shortestDistance = distance;
                    retVal = light;
                }
            }
            return retVal;
        }

        #endregion

        #region "Create camera"

        public Camera CreateCamera(Viewport viewport, string name)
        {
            Camera camera = new Camera();
            //camera.SetViewport(viewport);
            Engine.Instance.CameraManager.AddCamera(camera, name);
            //this.RegisterNode(camera, name);
            return camera;
        }

        #endregion

        #region "Create Light"

        public Light CreateLight(string name, bool castShadows)
        {
            Light light = new Light();

            if (castShadows)
                this.shadowLightList.Add(light);
            else
                this.normalLightList.Add(light);

            return light;
        }

        #endregion

    }

}


